package com.utilities.fastaUtilities;

import java.io.FileWriter;

import java.io.FileReader;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Set;
import java.util.TreeSet;

import com.utilities.UtilMethods;
import com.utilities.IO.FileUtilities;
import com.utilities.dataStructures.Range;
import com.utilities.parsers.gff3FileParser.ParseAllContigsFileParser;
import com.utilities.parsers.gff3FileParser.ParseEvents;
import com.utilities.parsers.gff3FileParser.ParseListener;

public class FindGenes implements Comparator<String>
{
	private String inputFile;
	private String outpuFile;
	private String geneRangesFile;
	private String geneInfoFile = null;
	private HashMap<String,ArrayList<String>> map = 
		new HashMap<String, ArrayList<String>>();
	private ArrayList<Range> geneRanges = new ArrayList<Range>();
	private String seqLength = null;

	public String getSeqLength() {
		return seqLength;
	}
	public ArrayList<Range> getGeneRanges()
	{
		return geneRanges;
	}
	public FindGenes setInputFile(String inputFile) 
	{
		this.inputFile = inputFile;
		return this;
	}
	
	public FindGenes setGeneInfoFile(String geneInfoFile) 
	{
		this.geneInfoFile = geneInfoFile;
		return this;
	}
	
	public FindGenes setGeneRangesFile(String inputFile) 
	{
		this.geneRangesFile = inputFile;
		return this;
		
	}

	public FindGenes setOutpuFile(String outpuFile) {
		this.outpuFile = outpuFile;
		return this;
	}

	/**
	 * This method reads the file and finds the genes.
	 * @throws IOException
	 */
	public void findGenes() throws IOException 
	{
		// start reading the file
		RandomAccessFile rand = new RandomAccessFile(inputFile, "r");
		String readLine = rand.readLine();
		System.out.println("Started reading gene positions");
		boolean flag_seqRead = false;
		boolean flag_info = (geneInfoFile != null);
		while(readLine != null)
		{
			readLine = readLine.trim();
			// read only the line that starts with this suffix
			if (readLine.startsWith("gene  "))
			{
				makeGeneMap(readLine,rand);
			}
			else if (flag_info && readLine.startsWith("LOCUS"))
			{
				seqLength = readLine.trim().replaceAll(" +", " ").split(" ")[2];
			}
			else if (readLine.startsWith("ORIGIN"))
			{
				FileUtilities.WriteToFile(geneRanges, geneRangesFile);
				System.out.println("Gene location read. Fetching the sequences from the FASTA part");
				flag_seqRead = true;
			}
			else if (flag_seqRead)
			{
				startProcessingSeq(rand,readLine);
				break;
			}
			readLine = rand.readLine();
		}
		
		rand.close();
	}

	public void makeGeneMap(String readLine, RandomAccessFile rand) throws IOException {
		/*
		 * split this line across space character
		 * params[first] will contain the key word gene and params[last]
		 * will be the position information
		 */
		String[] params = readLine.split(" ");
		String positionInfo = params[params.length - 1];
		
		// This is to check that we are not reading a gene info with no position info
		assert(positionInfo != null);
		
		// if the position info starts with complement then just ignore 
		// the complement part and extract whatever is inside it.
		if (positionInfo.startsWith("complement("))
		{
			positionInfo = positionInfo.substring(11,positionInfo.length()-1);
		}
		// make sure that there was only one, outer most complement and no more now.
		assert(!positionInfo.contains("complement"));
		
		// next line contains the gene Name in the form
		// /gene ="yabP"
		String temp = rand.readLine();
		String geneName = temp.trim().split("=")[1].replaceAll("\"", "");
		
		ArrayList<String> list = new ArrayList<String>();
		
		// if position info has a join then we have to change the geneName to 
		// geneName_C and remove the join keyword along with the brackets 
		// from the position info string.
		if (positionInfo.startsWith("join"))
		{
			geneName = geneName + "_C";
			positionInfo = positionInfo.substring(5,positionInfo.length()-1);
			// once join is removed once make sure there is no more join.
			assert(!positionInfo.contains("join"));
		}
		
		// if we have already read this gene before
		if (map.containsKey(geneName))
		{
			// fetch the positions list corresponding to this geneName
			list = map.get(geneName);
			
			// if the position info of this gene had join then its name must have "_C" 
			// suffix now.
			if (geneName.contains("_C"))
			{
				positionInfo = processJoin(positionInfo, geneName);
			}
			// add the positioninfo to the list
			list.add(positionInfo);
		}
		// if this is a new gene
		else
		{
			// if the position info of this gene had join then its name must have "_C" 
			// suffix now.
			if (geneName.contains("_C"))
			{
				positionInfo = processJoin(positionInfo, geneName);
			}
			list = new ArrayList<String>();
			list.add(positionInfo);
		}
		map.put(geneName, list);
//		System.out.println(positionInfo);
		String[] indexes = positionInfo.split("[.][.]");
		int lowerIndex = Integer.parseInt(indexes[0]);
		int upperIndex = Integer.parseInt(indexes[1]);
		if (lowerIndex > upperIndex)
		{
			int _temp = lowerIndex; lowerIndex = upperIndex; upperIndex = _temp;
		}
		Range range = new Range(Integer.parseInt(indexes[0]), Integer.parseInt(indexes[1]), geneName);
		geneRanges.add(range);
	}

	private void startProcessingSeq(RandomAccessFile rand, String readLine) throws IOException 
	{
		HashMap<String, String> hashMap = copyMap();
		HashMap<String,String> map = new HashMap<String, String>();
		LinkedList<String> keySet = new LinkedList<String>(hashMap.keySet());
		Collections.sort(keySet, new FindGenes());
		while(readLine != null)
		{
			readLine = readLine.trim();
			String[] params = readLine.split(" ");
			populate(map,hashMap,params[0],keySet);
			if (map.keySet().size()>0)
			{
				for (int i = 1; i <params.length;i++)
				{
					Iterator<String> keys = map.keySet().iterator();
					while(keys.hasNext())
					{
						String key = keys.next();
						int startingIndex = Integer.parseInt(key.split("\\.\\.")[0]);
						int endingIndex = Integer.parseInt(key.split("\\.\\.")[1]);
						int seqLength = endingIndex - startingIndex + 1;
						String value = map.get(key);
						StringBuffer strBuff = null;
						if (value == null) strBuff = new StringBuffer();
						else strBuff = new StringBuffer(value);
						
						char[] chars = params[i].toCharArray();
						boolean full = false;
						for (char c : chars)
						{
							strBuff.append(c);
							if (strBuff.length() == seqLength)
							{
								full = true;
								break;
							}
						}
						if (full)
						{
							String[] result = strBuff.toString().trim().split("\\|"); 
							writeTofile(result[0],result[1]);
							keys.remove();
						}
						else
						{
							map.put(key,strBuff.toString());
						}
					}
				}
			}
			readLine = rand.readLine();
		}
	}

	public void writeTofile(String key, String string) throws IOException
	{
		FileWriter fw = new FileWriter(this.outpuFile, true);
		fw.write(String.format(">%s\n%s\n", key,string));
		fw.flush();
		fw.close();
	}

	private static void populate(HashMap<String, String> map2,
			HashMap<String, String> hashMap, String string, LinkedList<String> keySet) 
	{
		Iterator<String> itr = keySet.iterator();
		while(itr.hasNext())
		{
			String key = itr.next();
			if (!map2.containsKey(key))
			{
				int currentLineIndex = Integer.parseInt(string);
				int startingIndex = Integer.parseInt(key.split("\\.\\.")[0]);
				if ((currentLineIndex + 60) >= startingIndex)
				{
					itr.remove();
					map2.put(key, hashMap.get(key)+"|");
					hashMap.remove(key);
				}
				else break;
			}
		}
	}

	private HashMap<String,String> copyMap() 
	{
		HashMap<String,String> PostionToName = new HashMap<String, String>();
		Set<String> keySet = map.keySet();
		for(String key : keySet)
		{
			ArrayList<String> list = map.get(key);
			for (String item : list)
			{
				assert(!PostionToName.containsKey(item));
				PostionToName.put(item, key);
			}
		}
		
//		Iterator<String> keys = map.keySet().iterator();
//		while(keys.hasNext())
//		{
//			String key = keys.next();
//			ArrayList<String> list = map.get(key);
//			for (String item : list)
//			{
//				assert(!PostionToName.containsKey(item));
//				PostionToName.put(item, key);
//			}
//			keys.remove();
//		}
//		assert(map.size()==0);
//		map.clear();
		return PostionToName;
	}

	/**
	 * This method takes the position info string which looks like 
	 * join(269502..269759,270988..271413)
	 * and puts each fragment i.e. 269502..269759 and 270988..271413
	 * in the map by assigning a new geneName. The new geneName is prepared 
	 * by concatenating geneName provided as parameter and an integer.
	 * @param positionInfo
	 * @param geneName
	 * @return
	 */
	private String processJoin(String positionInfo, String geneName) 
	{
		String newPositions;
		// get all the positional info
		String[] subParts = positionInfo.split(",");
		// create positional string corresponding to geneName
		// this will be <starting index of the first subpart>..<ending index of the last subpart>
		int startingIndex = Integer.parseInt(subParts[0].split("\\.\\.")[0]);
		int endIndex = Integer.parseInt(subParts[subParts.length - 1].split("\\.\\.")[1]);
		
		assert(startingIndex < endIndex);
		newPositions =  startingIndex+ ".." + endIndex;
		
		// Now for each fragment of the gene sequence create a new geneName.
		// and put the new geneName and position in the map.
		int i = 0;
		
		String newGeneName = geneName.substring(0,geneName.indexOf("_C")) + "_" +i;
		// it is possible that new geneName is already present
		// so we search of the highest index that has already been assigned.
		while (map.containsKey(newGeneName))
		{
			i++;
			newGeneName = geneName.substring(0,geneName.indexOf("_C")) + "_" +i;
			System.out.println(newGeneName);
		}
		
		// increment once more so that if insB_5 was the fragment already present
		// then the new fragments will be from 7 i.e. from insB_7
		if (i!=0)i++;
		// iterate over each fragment
		
		for (String subPart : subParts)
		{
			// put the new fragments into the map
			newGeneName = geneName.substring(0,geneName.indexOf("_C")) + "_" +i;
			ArrayList<String> temp1 = new ArrayList<String>();
			temp1.add(subPart.trim());
			map.put(newGeneName,temp1);
			i++;
		}
		return newPositions;
	}

	public void findMissingGenes(String geneFile) throws Exception
	{
		final Set<String> genes = OtherUtilities.fastaHeader(geneFile,null);
		ParseAllContigsFileParser allContigsFileParser = new ParseAllContigsFileParser();
		allContigsFileParser.addListener(new ParseListener() {
			
			@Override
			public void handleEvent(ParseEvents event) {
				try
				{
					switch((ParseEvents.EventTypes)event.getEventType())
					{
					case geneEncountered:
						String readLine = (String)event.getSource();
						String[] params = readLine.split("\\t");
						if (!UtilMethods.isNullOrEmpty(params[2]))
						{
							String Name = params[8].split(";")[2].split("=")[1];
							if (genes.contains(Name)) genes.remove(Name);
						}
						break;
					case seqStarted:
						break;
					}
				}
				catch(Exception e)
				{
					e.printStackTrace();
				}
			}
		});
		allContigsFileParser.parse(new FileReader(inputFile));
		
		if (genes.isEmpty()) System.out.println("All the genes in genes.fa are present in the gff3");
		else
		{
			FileWriter fw = new FileWriter(outpuFile);
			for(String gene : genes)
			{
				fw.append(String.format("%s\n",gene));
			}
			fw.flush();
			fw.close();
		}
	}
	
	public Set<String> stripNamesFromFastaHeaders(Set<String[]> headers)
	{
		Set<String> set = new TreeSet<String>();
		for (String[] header : headers)
		{
			set.add(header[0]);
		}
		return set;
	}
	
	public HashMap<String, ArrayList<String>> getMap() {
		return map;
	}

	@Override
	public int compare(String arg0, String arg1) 
	{
//		System.out.println(arg0 + " " + arg1);
		return Integer.parseInt(arg0.split("\\.\\.")[0]) - Integer.parseInt(arg1.split("\\.\\.")[0]); 
	}
}
